{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c33fc20",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入必要的库\n",
    "import os\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torch.optim.lr_scheduler import CosineAnnealingLR\n",
    "from torch.utils.data import DataLoader\n",
    "import torchvision\n",
    "import torchvision.transforms as transforms\n",
    "from timm.layers import trunc_normal_, DropPath\n",
    "from torchvision.datasets import CIFAR100\n",
    "import matplotlib.pyplot as plt\n",
    "from tqdm.notebook import tqdm  \n",
    "\n",
    "def set_seed(seed=42):\n",
    "    np.random.seed(seed)\n",
    "    torch.manual_seed(seed)\n",
    "    torch.cuda.manual_seed(seed)\n",
    "    torch.backends.cudnn.deterministic = True\n",
    "    torch.backends.cudnn.benchmark = True\n",
    "\n",
    "\n",
    "set_seed(42)\n",
    "\n",
    "# 创建保存目录\n",
    "save_dir = './pretrained_checkpoints'\n",
    "os.makedirs(save_dir, exist_ok=True)\n",
    "\n",
    "# 设置曲线保存路径\n",
    "curve_path = os.path.join(save_dir, 'training_curves.png')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81753932",
   "metadata": {},
   "outputs": [],
   "source": [
    "class LayerNorm(nn.Module):\n",
    "  \n",
    "    def __init__(self, normalized_shape, eps=1e-6, data_format=\"channels_last\"):\n",
    "        super().__init__()\n",
    "        self.weight = nn.Parameter(torch.ones(normalized_shape))\n",
    "        self.bias = nn.Parameter(torch.zeros(normalized_shape))\n",
    "        self.eps = eps\n",
    "        self.data_format = data_format\n",
    "        if self.data_format not in [\"channels_last\", \"channels_first\"]:\n",
    "            raise NotImplementedError \n",
    "        self.normalized_shape = (normalized_shape, )\n",
    "    \n",
    "    def forward(self, x):\n",
    "        if self.data_format == \"channels_last\":\n",
    "            return F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)\n",
    "        elif self.data_format == \"channels_first\":\n",
    "            u = x.mean(1, keepdim=True)\n",
    "            s = (x - u).pow(2).mean(1, keepdim=True)\n",
    "            x = (x - u) / torch.sqrt(s + self.eps)\n",
    "            x = self.weight[:, None, None] * x + self.bias[:, None, None]\n",
    "            return x\n",
    "\n",
    "class Block(nn.Module):\n",
    "    \"\"\"ConvNeXt Block\"\"\"\n",
    "    def __init__(self, dim, drop_path=0., layer_scale_init_value=1e-6):\n",
    "        super().__init__()\n",
    "        self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim) \n",
    "        self.norm = LayerNorm(dim, eps=1e-6)\n",
    "        self.pwconv1 = nn.Linear(dim, 4 * dim) \n",
    "        self.act = nn.GELU()\n",
    "        self.pwconv2 = nn.Linear(4 * dim, dim)\n",
    "        self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)), \n",
    "                                    requires_grad=True) if layer_scale_init_value > 0 else None\n",
    "        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()\n",
    "\n",
    "    def forward(self, x):\n",
    "        input = x\n",
    "        x = self.dwconv(x)\n",
    "        x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C)\n",
    "        x = self.norm(x)\n",
    "        x = self.pwconv1(x)\n",
    "        x = self.act(x)\n",
    "        x = self.pwconv2(x)\n",
    "        if self.gamma is not None:\n",
    "            x = self.gamma * x\n",
    "        x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W)\n",
    "\n",
    "        x = input + self.drop_path(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "987399f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ConvNeXt(nn.Module):\n",
    "    \"\"\"ConvNeXt\"\"\"\n",
    "    def __init__(self, in_chans=3, num_classes=100, \n",
    "                 depths=[3, 3, 9, 3], dims=[96, 192, 384, 768], drop_path_rate=0.1, \n",
    "                 layer_scale_init_value=1e-6, head_init_scale=1.):\n",
    "        super().__init__()\n",
    "\n",
    "        # 下采样层\n",
    "        self.downsample_layers = nn.ModuleList()\n",
    "        stem = nn.Sequential(\n",
    "            nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4),\n",
    "            LayerNorm(dims[0], eps=1e-6, data_format=\"channels_first\")\n",
    "        )\n",
    "        self.downsample_layers.append(stem)\n",
    "        for i in range(3):\n",
    "            downsample_layer = nn.Sequential(\n",
    "                LayerNorm(dims[i], eps=1e-6, data_format=\"channels_first\"),\n",
    "                nn.Conv2d(dims[i], dims[i+1], kernel_size=2, stride=2),\n",
    "            )\n",
    "            self.downsample_layers.append(downsample_layer)\n",
    "\n",
    "        # 主干网络阶段\n",
    "        self.stages = nn.ModuleList()\n",
    "        dp_rates=[x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] \n",
    "        cur = 0\n",
    "        for i in range(4):\n",
    "            stage = nn.Sequential(\n",
    "                *[Block(dim=dims[i], drop_path=dp_rates[cur + j], \n",
    "                layer_scale_init_value=layer_scale_init_value) for j in range(depths[i])]\n",
    "            )\n",
    "            self.stages.append(stage)\n",
    "            cur += depths[i]\n",
    "\n",
    "        # 分类头\n",
    "        self.norm = nn.LayerNorm(dims[-1], eps=1e-6)\n",
    "        self.head = nn.Linear(dims[-1], num_classes)\n",
    "\n",
    "        # 权重初始化\n",
    "        self.apply(self._init_weights)\n",
    "        self.head.weight.data.mul_(head_init_scale)\n",
    "        self.head.bias.data.mul_(head_init_scale)\n",
    "\n",
    "    def _init_weights(self, m):\n",
    "        if isinstance(m, (nn.Conv2d, nn.Linear)):\n",
    "            trunc_normal_(m.weight, std=.02)\n",
    "            nn.init.constant_(m.bias, 0)\n",
    "\n",
    "    def forward_features(self, x):\n",
    "        for i in range(4):\n",
    "            x = self.downsample_layers[i](x)\n",
    "            x = self.stages[i](x)\n",
    "        return self.norm(x.mean([-2, -1])) # 全局平均池化\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.forward_features(x)\n",
    "        x = self.head(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba5e293a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 预训练模型URL\n",
    "model_urls = {\n",
    "    \"convnext_tiny_1k\": \"https://dl.fbaipublicfiles.com/convnext/convnext_tiny_1k_224_ema.pth\",\n",
    "    \"convnext_small_1k\": \"https://dl.fbaipublicfiles.com/convnext/convnext_small_1k_224_ema.pth\",\n",
    "    \"convnext_base_1k\": \"https://dl.fbaipublicfiles.com/convnext/convnext_base_1k_224_ema.pth\",\n",
    "    \"convnext_large_1k\": \"https://dl.fbaipublicfiles.com/convnext/convnext_large_1k_224_ema.pth\",\n",
    "}\n",
    "\n",
    "def convnext_tiny_cifar100(pretrained=True):\n",
    "    \"\"\"创建预训练的ConvNeXt Tiny模型\"\"\"\n",
    "    model = ConvNeXt(depths=[3, 3, 9, 3], dims=[96, 192, 384, 768], num_classes=100)\n",
    "    \n",
    "    if pretrained:\n",
    "        # 加载预训练权重\n",
    "        url = model_urls['convnext_tiny_1k']\n",
    "        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location=\"cpu\", check_hash=True)\n",
    "        \n",
    "        # 移除分类头部分（因为CIFAR100是100类，而ImageNet是1000类）\n",
    "        if 'model' in checkpoint:\n",
    "            checkpoint = checkpoint['model']\n",
    "        if 'head.weight' in checkpoint:\n",
    "            checkpoint.pop('head.weight')\n",
    "        if 'head.bias' in checkpoint:\n",
    "            checkpoint.pop('head.bias')\n",
    "        \n",
    "        # 加载预训练权重\n",
    "        model.load_state_dict(checkpoint, strict=False)\n",
    "        print(\"预训练权重加载完成！\")\n",
    "    \n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3723bfa0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  1%|          | 2.03M/169M [00:34<46:58, 59.3kB/s]  \n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[5], line 28\u001b[0m\n\u001b[0;32m     25\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m train_loader, valid_loader\n\u001b[0;32m     27\u001b[0m \u001b[38;5;66;03m# 获取数据加载器\u001b[39;00m\n\u001b[1;32m---> 28\u001b[0m train_loader, valid_loader \u001b[38;5;241m=\u001b[39m \u001b[43mget_cifar100_loaders\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbatch_size\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m64\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m     29\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m数据集大小 - 训练集: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(train_loader\u001b[38;5;241m.\u001b[39mdataset)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m, 验证集: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(valid_loader\u001b[38;5;241m.\u001b[39mdataset)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
      "Cell \u001b[1;32mIn[5], line 11\u001b[0m, in \u001b[0;36mget_cifar100_loaders\u001b[1;34m(batch_size, num_workers)\u001b[0m\n\u001b[0;32m      4\u001b[0m transform \u001b[38;5;241m=\u001b[39m transforms\u001b[38;5;241m.\u001b[39mCompose([\n\u001b[0;32m      5\u001b[0m     transforms\u001b[38;5;241m.\u001b[39mResize(\u001b[38;5;241m224\u001b[39m),  \u001b[38;5;66;03m# ConvNeXt期望224x224输入\u001b[39;00m\n\u001b[0;32m      6\u001b[0m     transforms\u001b[38;5;241m.\u001b[39mToTensor(),\n\u001b[0;32m      7\u001b[0m     transforms\u001b[38;5;241m.\u001b[39mNormalize(mean\u001b[38;5;241m=\u001b[39m[\u001b[38;5;241m0.5071\u001b[39m, \u001b[38;5;241m0.4867\u001b[39m, \u001b[38;5;241m0.4408\u001b[39m], std\u001b[38;5;241m=\u001b[39m[\u001b[38;5;241m0.2675\u001b[39m, \u001b[38;5;241m0.2565\u001b[39m, \u001b[38;5;241m0.2761\u001b[39m])\n\u001b[0;32m      8\u001b[0m ])\n\u001b[0;32m     10\u001b[0m \u001b[38;5;66;03m# 加载CIFAR100数据集\u001b[39;00m\n\u001b[1;32m---> 11\u001b[0m train_dataset \u001b[38;5;241m=\u001b[39m \u001b[43mCIFAR100\u001b[49m\u001b[43m(\u001b[49m\u001b[43mroot\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m./data\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtrain\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdownload\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtransform\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtransform\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     12\u001b[0m valid_dataset \u001b[38;5;241m=\u001b[39m CIFAR100(root\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m./data\u001b[39m\u001b[38;5;124m'\u001b[39m, train\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, download\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, transform\u001b[38;5;241m=\u001b[39mtransform)\n\u001b[0;32m     14\u001b[0m \u001b[38;5;66;03m# 创建数据加载器\u001b[39;00m\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\site-packages\\torchvision\\datasets\\cifar.py:66\u001b[0m, in \u001b[0;36mCIFAR10.__init__\u001b[1;34m(self, root, train, transform, target_transform, download)\u001b[0m\n\u001b[0;32m     63\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtrain \u001b[38;5;241m=\u001b[39m train  \u001b[38;5;66;03m# training set or test set\u001b[39;00m\n\u001b[0;32m     65\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m download:\n\u001b[1;32m---> 66\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdownload\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     68\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_integrity():\n\u001b[0;32m     69\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDataset not found or corrupted. You can use download=True to download it\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\site-packages\\torchvision\\datasets\\cifar.py:139\u001b[0m, in \u001b[0;36mCIFAR10.download\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m    137\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_integrity():\n\u001b[0;32m    138\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m--> 139\u001b[0m \u001b[43mdownload_and_extract_archive\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mroot\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfilename\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfilename\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmd5\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtgz_md5\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\site-packages\\torchvision\\datasets\\utils.py:391\u001b[0m, in \u001b[0;36mdownload_and_extract_archive\u001b[1;34m(url, download_root, extract_root, filename, md5, remove_finished)\u001b[0m\n\u001b[0;32m    388\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m filename:\n\u001b[0;32m    389\u001b[0m     filename \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mbasename(url)\n\u001b[1;32m--> 391\u001b[0m \u001b[43mdownload_url\u001b[49m\u001b[43m(\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdownload_root\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfilename\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmd5\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    393\u001b[0m archive \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(download_root, filename)\n\u001b[0;32m    394\u001b[0m extract_archive(archive, extract_root, remove_finished)\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\site-packages\\torchvision\\datasets\\utils.py:130\u001b[0m, in \u001b[0;36mdownload_url\u001b[1;34m(url, root, filename, md5, max_redirect_hops)\u001b[0m\n\u001b[0;32m    128\u001b[0m \u001b[38;5;66;03m# download the file\u001b[39;00m\n\u001b[0;32m    129\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 130\u001b[0m     \u001b[43m_urlretrieve\u001b[49m\u001b[43m(\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    131\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (urllib\u001b[38;5;241m.\u001b[39merror\u001b[38;5;241m.\u001b[39mURLError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m e:  \u001b[38;5;66;03m# type: ignore[attr-defined]\u001b[39;00m\n\u001b[0;32m    132\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m url[:\u001b[38;5;241m5\u001b[39m] \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhttps\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\site-packages\\torchvision\\datasets\\utils.py:30\u001b[0m, in \u001b[0;36m_urlretrieve\u001b[1;34m(url, filename, chunk_size)\u001b[0m\n\u001b[0;32m     28\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m urllib\u001b[38;5;241m.\u001b[39mrequest\u001b[38;5;241m.\u001b[39murlopen(urllib\u001b[38;5;241m.\u001b[39mrequest\u001b[38;5;241m.\u001b[39mRequest(url, headers\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUser-Agent\u001b[39m\u001b[38;5;124m\"\u001b[39m: USER_AGENT})) \u001b[38;5;28;01mas\u001b[39;00m response:\n\u001b[0;32m     29\u001b[0m     \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(filename, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwb\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m fh, tqdm(total\u001b[38;5;241m=\u001b[39mresponse\u001b[38;5;241m.\u001b[39mlength, unit\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mB\u001b[39m\u001b[38;5;124m\"\u001b[39m, unit_scale\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m pbar:\n\u001b[1;32m---> 30\u001b[0m         \u001b[38;5;28;01mwhile\u001b[39;00m chunk \u001b[38;5;241m:=\u001b[39m \u001b[43mresponse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchunk_size\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[0;32m     31\u001b[0m             fh\u001b[38;5;241m.\u001b[39mwrite(chunk)\n\u001b[0;32m     32\u001b[0m             pbar\u001b[38;5;241m.\u001b[39mupdate(\u001b[38;5;28mlen\u001b[39m(chunk))\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\http\\client.py:458\u001b[0m, in \u001b[0;36mHTTPResponse.read\u001b[1;34m(self, amt)\u001b[0m\n\u001b[0;32m    455\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m amt \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m    456\u001b[0m     \u001b[38;5;66;03m# Amount is given, implement using readinto\u001b[39;00m\n\u001b[0;32m    457\u001b[0m     b \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mbytearray\u001b[39m(amt)\n\u001b[1;32m--> 458\u001b[0m     n \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreadinto\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    459\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mmemoryview\u001b[39m(b)[:n]\u001b[38;5;241m.\u001b[39mtobytes()\n\u001b[0;32m    460\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m    461\u001b[0m     \u001b[38;5;66;03m# Amount is not given (unbounded read) so we must check self.length\u001b[39;00m\n\u001b[0;32m    462\u001b[0m     \u001b[38;5;66;03m# and self.chunked\u001b[39;00m\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\http\\client.py:502\u001b[0m, in \u001b[0;36mHTTPResponse.readinto\u001b[1;34m(self, b)\u001b[0m\n\u001b[0;32m    497\u001b[0m         b \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmemoryview\u001b[39m(b)[\u001b[38;5;241m0\u001b[39m:\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mlength]\n\u001b[0;32m    499\u001b[0m \u001b[38;5;66;03m# we do not use _safe_read() here because this may be a .will_close\u001b[39;00m\n\u001b[0;32m    500\u001b[0m \u001b[38;5;66;03m# connection, and the user is reading more bytes than will be provided\u001b[39;00m\n\u001b[0;32m    501\u001b[0m \u001b[38;5;66;03m# (for example, reading in 1k chunks)\u001b[39;00m\n\u001b[1;32m--> 502\u001b[0m n \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreadinto\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    503\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m n \u001b[38;5;129;01mand\u001b[39;00m b:\n\u001b[0;32m    504\u001b[0m     \u001b[38;5;66;03m# Ideally, we would raise IncompleteRead if the content-length\u001b[39;00m\n\u001b[0;32m    505\u001b[0m     \u001b[38;5;66;03m# wasn't satisfied, but it might break compatibility.\u001b[39;00m\n\u001b[0;32m    506\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_close_conn()\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\socket.py:704\u001b[0m, in \u001b[0;36mSocketIO.readinto\u001b[1;34m(self, b)\u001b[0m\n\u001b[0;32m    702\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m    703\u001b[0m     \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 704\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_sock\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrecv_into\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    705\u001b[0m     \u001b[38;5;28;01mexcept\u001b[39;00m timeout:\n\u001b[0;32m    706\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_timeout_occurred \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\ssl.py:1241\u001b[0m, in \u001b[0;36mSSLSocket.recv_into\u001b[1;34m(self, buffer, nbytes, flags)\u001b[0m\n\u001b[0;32m   1237\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m flags \u001b[38;5;241m!=\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m   1238\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[0;32m   1239\u001b[0m           \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnon-zero flags not allowed in calls to recv_into() on \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m\n\u001b[0;32m   1240\u001b[0m           \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m)\n\u001b[1;32m-> 1241\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnbytes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1242\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m   1243\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39mrecv_into(buffer, nbytes, flags)\n",
      "File \u001b[1;32md:\\Python3.9\\lib\\ssl.py:1099\u001b[0m, in \u001b[0;36mSSLSocket.read\u001b[1;34m(self, len, buffer)\u001b[0m\n\u001b[0;32m   1097\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m   1098\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m buffer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m-> 1099\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_sslobj\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1100\u001b[0m     \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m   1101\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_sslobj\u001b[38;5;241m.\u001b[39mread(\u001b[38;5;28mlen\u001b[39m)\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "def get_cifar100_loaders(batch_size=64, num_workers=2):\n",
    "    \"\"\"数据加载和预处理 - 去掉数据增强\"\"\"\n",
    "    # 简单的数据转换，无数据增强\n",
    "    transform = transforms.Compose([\n",
    "        transforms.Resize(224),  # ConvNeXt期望224x224输入\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean=[0.5071, 0.4867, 0.4408], std=[0.2675, 0.2565, 0.2761])\n",
    "    ])\n",
    "    \n",
    "    # 加载CIFAR100数据集\n",
    "    train_dataset = CIFAR100(root='./data', train=True, download=True, transform=transform)\n",
    "    valid_dataset = CIFAR100(root='./data', train=False, download=True, transform=transform)\n",
    "    \n",
    "    # 创建数据加载器\n",
    "    train_loader = DataLoader(\n",
    "        train_dataset, batch_size=batch_size, shuffle=True,\n",
    "        num_workers=num_workers, pin_memory=True\n",
    "    )\n",
    "    \n",
    "    valid_loader = DataLoader(\n",
    "        valid_dataset, batch_size=batch_size, shuffle=False,\n",
    "        num_workers=num_workers, pin_memory=True\n",
    "    )\n",
    "    \n",
    "    return train_loader, valid_loader\n",
    "\n",
    "# 获取数据加载器\n",
    "train_loader, valid_loader = get_cifar100_loaders(batch_size=64)\n",
    "print(f\"数据集大小 - 训练集: {len(train_loader.dataset)}, 验证集: {len(valid_loader.dataset)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "989f1ec1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_one_epoch(model, train_loader, criterion, optimizer, device):\n",
    "    \"\"\"训练一个epoch\"\"\"\n",
    "    model.train()\n",
    "    train_loss = 0\n",
    "    correct = 0\n",
    "    total = 0\n",
    "    \n",
    "    pbar = tqdm(train_loader, desc='训练中')\n",
    "    for batch_idx, (inputs, targets) in enumerate(pbar):\n",
    "        inputs, targets = inputs.to(device), targets.to(device)\n",
    "        \n",
    "        optimizer.zero_grad()\n",
    "        outputs = model(inputs)\n",
    "        loss = criterion(outputs, targets)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        train_loss += loss.item()\n",
    "        _, predicted = outputs.max(1)\n",
    "        total += targets.size(0)\n",
    "        correct += predicted.eq(targets).sum().item()\n",
    "        \n",
    "        pbar.set_postfix({\n",
    "            'loss': train_loss/(batch_idx+1),\n",
    "            'acc': 100.*correct/total\n",
    "        })\n",
    "    \n",
    "    return train_loss/len(train_loader), 100.*correct/total\n",
    "\n",
    "def validate(model, valid_loader, criterion, device):\n",
    "    \"\"\"验证函数\"\"\"\n",
    "    model.eval()\n",
    "    valid_loss = 0\n",
    "    correct = 0\n",
    "    total = 0\n",
    "    \n",
    "    with torch.no_grad():\n",
    "        pbar = tqdm(valid_loader, desc='验证中')\n",
    "        for batch_idx, (inputs, targets) in enumerate(pbar):\n",
    "            inputs, targets = inputs.to(device), targets.to(device)\n",
    "            \n",
    "            outputs = model(inputs)\n",
    "            loss = criterion(outputs, targets)\n",
    "            \n",
    "            valid_loss += loss.item()\n",
    "            _, predicted = outputs.max(1)\n",
    "            total += targets.size(0)\n",
    "            correct += predicted.eq(targets).sum().item()\n",
    "            \n",
    "            pbar.set_postfix({\n",
    "                'loss': valid_loss/(batch_idx+1),\n",
    "                'acc': 100.*correct/total\n",
    "            })\n",
    "    \n",
    "    return valid_loss/len(valid_loader), 100.*correct/total\n",
    "\n",
    "def plot_training_curves(train_losses, valid_losses, train_accs, valid_accs, save_path=curve_path):\n",
    "    \"\"\"绘制训练曲线并保存\"\"\"\n",
    "    plt.figure(figsize=(14, 5))\n",
    "    \n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.plot(train_losses, label='训练损失')\n",
    "    plt.plot(valid_losses, label='验证损失')\n",
    "    plt.xlabel('轮次')\n",
    "    plt.ylabel('损失')\n",
    "    plt.legend()\n",
    "    plt.title('损失曲线')\n",
    "    \n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.plot(train_accs, label='训练准确率')\n",
    "    plt.plot(valid_accs, label='验证准确率')\n",
    "    plt.xlabel('轮次')\n",
    "    plt.ylabel('准确率 (%)')\n",
    "    plt.legend()\n",
    "    plt.title('准确率曲线')\n",
    "    \n",
    "    plt.tight_layout()\n",
    "    \n",
    "    # 保存图像\n",
    "    plt.savefig(save_path)\n",
    "    \n",
    "    # 在Jupyter中显示图像\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f53becf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 设置训练参数\n",
    "lr = 1e-5                 # 学习率\n",
    "batch_size = 64           # 批次大小  \n",
    "num_epochs = 10           # 训练轮次\n",
    "weight_decay = 1e-4       # 权重衰减\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "\n",
    "print(f\"使用设备: {device}\")\n",
    "\n",
    "# 创建模型（预训练）\n",
    "model = convnext_tiny_cifar100(pretrained=True)\n",
    "model = model.to(device)\n",
    "\n",
    "# 定义损失函数和优化器\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay)\n",
    "scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc3ff81a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 训练循环\n",
    "train_losses, valid_losses = [], []\n",
    "train_accs, valid_accs = [], []\n",
    "best_acc = 0\n",
    "\n",
    "print(f\"开始预训练模型微调，总共{num_epochs}轮...\")\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    print(f\"\\n轮次: {epoch+1}/{num_epochs}\")\n",
    "    \n",
    "    # 训练\n",
    "    train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)\n",
    "    train_losses.append(train_loss)\n",
    "    train_accs.append(train_acc)\n",
    "    \n",
    "    # 验证\n",
    "    valid_loss, valid_acc = validate(model, valid_loader, criterion, device)\n",
    "    valid_losses.append(valid_loss)\n",
    "    valid_accs.append(valid_acc)\n",
    "    \n",
    "    # 更新学习率\n",
    "    scheduler.step()\n",
    "    \n",
    "    # 保存最佳模型\n",
    "    if valid_acc > best_acc:\n",
    "        best_acc = valid_acc\n",
    "        torch.save({\n",
    "            'epoch': epoch,\n",
    "            'model_state_dict': model.state_dict(),\n",
    "            'optimizer_state_dict': optimizer.state_dict(),\n",
    "            'accuracy': valid_acc,\n",
    "        }, os.path.join(save_dir, 'best_model.pth'))\n",
    "        print(f\"保存最佳模型，准确率: {valid_acc:.2f}%\")\n",
    "    \n",
    "    # 绘制训练曲线\n",
    "    plot_training_curves(train_losses, valid_losses, train_accs, valid_accs, curve_path)\n",
    "\n",
    "print(f\"训练完成! 最佳验证准确率: {best_acc:.2f}%\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0373a25",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 加载最佳模型测试\n",
    "best_model = convnext_tiny_cifar100(pretrained=False)\n",
    "checkpoint = torch.load(os.path.join(save_dir, 'best_model.pth'), map_location=device)\n",
    "best_model.load_state_dict(checkpoint['model_state_dict'])\n",
    "best_model = best_model.to(device)\n",
    "\n",
    "# 测试最佳模型\n",
    "test_loss, test_acc = validate(best_model, valid_loader, criterion, device)\n",
    "print(f\"最佳模型在测试集上的准确率: {test_acc:.2f}%\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
